/*
 * Copyright (C) 2012-2020 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.eventbus;

import org.greenrobot.eventbus.android.AndroidComponents;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Creates EventBus instances with custom parameters and also allows to install a custom default
 * EventBus instance. Create a new builder using {@link EventBus#builder()}.
 */
@SuppressWarnings("unused")
public class EventBusBuilder {
  private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

  boolean logSubscriberExceptions = true;
  boolean logNoSubscriberMessages = true;
  boolean sendSubscriberExceptionEvent = true;
  boolean sendNoSubscriberEvent = true;
  boolean throwSubscriberException;
  boolean eventInheritance = true;
  boolean ignoreGeneratedIndex;
  boolean strictMethodVerification;
  ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
  List<Class<?>> skipMethodVerificationForClasses;
  List<SubscriberInfoIndex> subscriberInfoIndexes;
  Logger logger;
  MainThreadSupport mainThreadSupport;

  EventBusBuilder() {}

  /** Default: true */
  public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
    this.logSubscriberExceptions = logSubscriberExceptions;
    return this;
  }

  /** Default: true */
  public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
    this.logNoSubscriberMessages = logNoSubscriberMessages;
    return this;
  }

  /** Default: true */
  public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
    this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
    return this;
  }

  /** Default: true */
  public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
    this.sendNoSubscriberEvent = sendNoSubscriberEvent;
    return this;
  }

  /**
   * Fails if an subscriber throws an exception (default: false).
   *
   * <p>Tip: Use this with BuildConfig.DEBUG to let the app crash in DEBUG mode (only). This way,
   * you won't miss exceptions during development.
   */
  public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {
    this.throwSubscriberException = throwSubscriberException;
    return this;
  }

  /**
   * By default, EventBus considers the event class hierarchy (subscribers to super classes will be
   * notified). Switching this feature off will improve posting of events. For simple event classes
   * extending Object directly, we measured a speed up of 20% for event posting. For more complex
   * event hierarchies, the speed up should be greater than 20%.
   *
   * <p>However, keep in mind that event posting usually consumes just a small proportion of CPU
   * time inside an app, unless it is posting at high rates, e.g. hundreds/thousands of events per
   * second.
   */
  public EventBusBuilder eventInheritance(boolean eventInheritance) {
    this.eventInheritance = eventInheritance;
    return this;
  }

  /**
   * Provide a custom thread pool to EventBus used for async and background event delivery. This is
   * an advanced setting to that can break things: ensure the given ExecutorService won't get stuck
   * to avoid undefined behavior.
   */
  public EventBusBuilder executorService(ExecutorService executorService) {
    this.executorService = executorService;
    return this;
  }

  /**
   * Method name verification is done for methods starting with onEvent to avoid typos; using this
   * method you can exclude subscriber classes from this check. Also disables checks for method
   * modifiers (public, not static nor abstract).
   */
  public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
    if (skipMethodVerificationForClasses == null) {
      skipMethodVerificationForClasses = new ArrayList<>();
    }
    skipMethodVerificationForClasses.add(clazz);
    return this;
  }

  /** Forces the use of reflection even if there's a generated index (default: false). */
  public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
    this.ignoreGeneratedIndex = ignoreGeneratedIndex;
    return this;
  }

  /** Enables strict method verification (default: false). */
  public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
    this.strictMethodVerification = strictMethodVerification;
    return this;
  }

  /** Adds an index generated by EventBus' annotation preprocessor. */
  public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    if (subscriberInfoIndexes == null) {
      subscriberInfoIndexes = new ArrayList<>();
    }
    subscriberInfoIndexes.add(index);
    return this;
  }

  /**
   * Set a specific log handler for all EventBus logging.
   *
   * <p>By default, all logging is via {@code android.util.Log} on Android or System.out on JVM.
   */
  public EventBusBuilder logger(Logger logger) {
    this.logger = logger;
    return this;
  }

  Logger getLogger() {
    if (logger != null) {
      return logger;
    } else {
      return Logger.Default.get();
    }
  }

  MainThreadSupport getMainThreadSupport() {
    if (mainThreadSupport != null) {
      return mainThreadSupport;
    } else if (AndroidComponents.areAvailable()) {
      return AndroidComponents.get().defaultMainThreadSupport;
    } else {
      return null;
    }
  }

  public EventBus installDefaultEventBus() {
    return installDefaultEventBus(false);
  }

  /**
   * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders'
   * values. Must be done only once before the first usage of the default EventBus.
   *
   * @throws EventBusException if there's already a default EventBus instance in place
   */
  public EventBus installDefaultEventBus(boolean ignoreIfInstalled) {
    synchronized (EventBus.class) {
      if (EventBus.defaultInstance != null) {
        if (ignoreIfInstalled) {
          return EventBus.defaultInstance;
        }

        throw new EventBusException(
            "Default instance already exists."
                + " It may be only set once before it's used the first time to ensure consistent behavior.");
      }
      EventBus.defaultInstance = build();
      return EventBus.defaultInstance;
    }
  }

  /** Builds an EventBus based on the current configuration. */
  public EventBus build() {
    return new EventBus(this);
  }
}
